using System;
using Microsoft.SPOT;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware.NetduinoPlus;
using SecretLabs.NETMF.Hardware;
using Netduino.Sensor.Unit;
using System.IO;

namespace Netduino.Sensor
{
    /// <summary>
    /// A simple web server that serves webpages to show the values of attached sensors 
    /// on a NetduinoPlus device.
    /// </summary>
    /// <remarks>The sensor data comes from in-memory temporary storage, which is filled
    /// in a background thread.</remarks>
    /// <devdoc>TODO, currently the values are only simulated.</devdoc>
    public class WebServer : IDisposable
    {
        /// <summary>
        /// Socket to listen to web requests.
        /// </summary>
        private Socket _socket = null;
        
        /// <summary>
        /// An open connection to onbaord led so we can blink it with every request
        /// </summary>
        private OutputPort _led = new OutputPort(Pins.ONBOARD_LED, false);
        
        /// <summary>
        /// Gets or sets an instance of a collector, that provides sensor data.
        /// </summary>
        private Data.Collector Collector { get; set; }

        /// <summary>
        /// Creates a webserver, listening on port 80.
        /// </summary>
        /// <remarks>. It expects to get it's IP address through
        /// DHCP, where it acts as client.
        /// The data served comes from the provided collector.</remarks>
        public WebServer(Data.Collector collector)
        {
            //keep reference to the collector
            Collector = collector;

            //Initialize Socket class
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //Request and bind to an IP from DHCP server
            _socket.Bind(new IPEndPoint(IPAddress.Any, 80));
            //Debug print our IP address
            Debug.Print(Microsoft.SPOT.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()[0].IPAddress);
            //Start listen for web requests, with short queue, to keep the load low on the netduino
            _socket.Listen(3);
            AcceptRequests();
        }

        /// <summary>
        /// Continuously accepts requests and serves them.
        /// </summary>
        public void AcceptRequests()
        {
            while (true)
            {
                using (Socket connectionSocket = _socket.Accept()) //serve a new connection request
                {
                    //Get clients IP
                    IPEndPoint clientIP = connectionSocket.RemoteEndPoint as IPEndPoint;
                    EndPoint clientEndPoint = connectionSocket.RemoteEndPoint;
                    int bytesReceived = connectionSocket.Available;
                    if (bytesReceived > 0)
                    {
                        //Get request
                        byte[] buffer = new byte[bytesReceived];
                        int byteCount = connectionSocket.Receive(buffer, bytesReceived, SocketFlags.None);
                        string request = new string(Encoding.UTF8.GetChars(buffer));
                        Debug.Print(request);

                        ///Serve the request, depending on the requested resource
                        if (request.IndexOf(@"GET /display.htm") >= 0) //display page requested?
                        {
                            String response = GetDisplayPage();
                            Serve(response, connectionSocket);
                        }
                        else if (request.IndexOf("GET /data.json?") == 0) //json update request?
                        {
                            String response = GetJsonResponse();
                            Serve(response, connectionSocket);
                        }
                        else if (request.IndexOf(@"/favicon.ico") == 0) //favicon requested?
                        {
                            ServeWith404(String.Empty, connectionSocket);
                        }
                        else if 
                            (
                                (request.IndexOf("GET /index.htm") == 0) || //index page requested?
                                (request.IndexOf("GET / HTTP") == 0) //root requested?
                            )
                        {
                            String response = GetIndexPage();
                            Serve(response, connectionSocket);
                        }
                        else //do not know what to serve, show 404
                        {
                            ServeWith404(String.Empty, connectionSocket);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Serves the response to the client with a status code of 200 OK.
        /// </summary>
        /// <param name="response">The response to send.</param>
        /// <param name="socket">The socket to send the response with.</param>
        private void Serve(string response, Socket socket)
        {
            string header = "HTTP/1.0 200 OK\r\nContent-Type: text; charset=utf-8\r\nContent-Length: " + response.Length.ToString() + "\r\nConnection: close\r\n\r\n";
            SendResponse(response, socket, header);
        }

        /// <summary>
        /// Serves the response to the client with a status code of 404 Not Found.
        /// </summary>
        /// <param name="response">The response to send.</param>
        /// <param name="socket">The socket to send the response with.</param>
        private void ServeWith404(string response, Socket socket)
        {
            string header = "HTTP/1.0 404 Not Found\r\nContent-Type: text; charset=utf-8\r\nContent-Length: " + response.Length.ToString() + "\r\nConnection: close\r\n\r\n";
            SendResponse(response, socket, header);
        }

        /// <summary>
        /// Sends the header and response to the client using the given socket. Blinks the LED afterwards.
        /// </summary>
        /// <param name="response"></param>
        /// <param name="socket"></param>
        /// <param name="header"></param>
        private void SendResponse(string response, Socket socket, string header)
        {
            socket.Send(Encoding.UTF8.GetBytes(header), header.Length, SocketFlags.None);

            var sendStream = new NetworkStream(socket, false);



            //socket.Send(Encoding.UTF8.GetBytes(response), response.Length, SocketFlags.None);
            //Blink();
        }

        /// <summary>
        /// Blinks the onboard LED
        /// </summary>
        private void Blink()
        {
            _led.Write(true);
            Thread.Sleep(100);
            _led.Write(false);
        }

        /// <summary>
        /// Gets the index page from the resources.
        /// </summary>
        /// <returns></returns>
        private string GetIndexPage()
        {
            var content = Resources.GetString(Resources.StringResources.index);
            return content;
        }

        /// <summary>
        /// Gets a resonse that is a JSON of the available samples.
        /// </summary>
        /// <returns></returns>
        private string GetJsonResponse()
        {
            if (
                (Collector.Data != null) &&
                (Collector.Data.Count > 0)
                )//we have data to serve?
            {
                String jsonData2 = @"["; //start array
                foreach (var item in Collector.Data)
                {
                    if (!item.Equals(Collector.Data[0])) //we are not at the first item?
                    {
                        jsonData2 += @","; //add separator
                    }
                    jsonData2 += (item as Sample).ToJson();
                }
                jsonData2 += @"]"; //terminate array
                return jsonData2;
            }
            else 
            {
                return new Sample(-1, _unspecifiedUnit).ToJson(); //fake data
            }
        }

        /// <summary>
        /// Unspecified unit for serving data requests with fake data when there is no real data available.
        /// </summary>
        private IUnit _unspecifiedUnit = new SpecifiedUnit("unspecified", "-");

        /// <summary>
        /// Gets the display page from the resources.
        /// </summary>
        /// <remarks>The display page is a complete, self-updating http page, that enalbles the client
        /// to show the available sensor values.</remarks>
        /// <returns></returns>
        private static string GetDisplayPage()
        {
            var content = Resources.GetString(Resources.StringResources.display);
            return content;
        }

        #region IDisposable Members
        ~WebServer()
        {
            Dispose();
        }
        public void Dispose()
        {
            if (_socket != null)
                _socket.Close();
        }
        #endregion
    }
}
